## FocusEmail - Handle all of the email related activities
## Copyright (C) January 26, 2005 Erick P. Bodine
## email: erick_bodine@comcast.net
## webaddress: 

## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or any later version.

## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.

## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


# NOTE: The email is composed of the email.header, email.body, email.footer templates
#  These together comprise the template together.
# TODO: use TextCtrl.SaveFile() in __validateTemplate() method


"""
NAME
FocusEmail.py

SYNOPSIS


DESCRIPTION


AUTHOR
Erick P Bodine
"""

import wx

import os
import re
import sys
import shutil
import socket
import logging
import smtplib
import mimetypes
from email import Encoders
from email.Message import Message
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart

__author__ = "$Author: ebodine $"
__date__ = "$Date: 2005-03-13 21:20:46 -0700 (Sun, 13 Mar 2005) $"
__version__ = "$Revision: 7 $"

log = logging.getLogger("myfocus")


class FocusEmailException(Exception):
    """ Base class for FocusEmail exceptions. """
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return `self.value`


class EditTemplateDialog(wx.Dialog):
    """ Dialog for editing the email template.
    The template consists of a header, body & footer.
    
    The text in the control is passed in, validated and saved to the
    template file upon success.
    
    @HEADER
    @BODY
    @FOOTER
    %Project%
    %Log%
    %Task%
    
    Checks during template parsing.
    1. If just %Project% - stupid idea
    2. Need all 3 paragraph separators @HEADER, @BODY, @FOOTER
    
    Attributes:
        template_file = The email template file
    """
    def __init__(self, parent, template_file):
        wx.Dialog.__init__(self, parent, -1, title="Edit Email Template", pos=wx.DefaultPosition, 
                            size=wx.DefaultSize, style=wx.DEFAULT_DIALOG_STYLE)
        
        # TODO: Need to set up EVT_TEXT event to change self.contents_changed
                            
        self.contents_changed = False
        self.templateFile = template_file
                            
        #--- EditTemplateDialog controls ---#
        self.text_box = wx.TextCtrl(self, -1, "", size=(300,350), style=wx.TE_MULTILINE)
        self.horizontal = wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL)
        self.save_button = wx.Button(self, wx.ID_OK, "Save")
        self.cancel_button = wx.Button(self, wx.ID_CANCEL, "Cancel")
        
        #--- End EditTemplateDialog controls ---#
        
        self.__set_properties()
        self.__do_layout()
        
        self.CenterOnScreen()
        
        self.value = self.ShowModal()
        
        if self.value == wx.ID_CANCEL:
            self.Destroy()
            
        elif self.value == wx.ID_OK:
            if self.__validateTemplate(self.text_box.GetValue()):
                self.text_box.SaveFile(self.templateFile)
            
    #------------------------------------------------------#
    #         EditTemplateDialog private methods           #
    #------------------------------------------------------#
        
    def __set_properties(self):
        """ Set control properties. """
        
        # For some reason LoadFile() causes a GTK+ assertion on my Linux
        #  box at work.
        if sys.platform == 'win32':
            if not self.text_box.LoadFile(self.templateFile):
                print "Unable to load %s" % self.templateFile
        else:
            try:
                self.text_box.WriteText(open(self.templateFile).read())
            except (IOError,OSError), error:
                print "Unable to load %s: (%s)" % (self.templateFile, str(error))
        
    def __do_layout(self):
        """ Layout the controls. """
        self.rootSizer = wx.BoxSizer(wx.VERTICAL)
        self.staticSizer = wx.StaticBoxSizer(wx.StaticBox(self, -1, "Edit Email Template"), 
                                                wx.VERTICAL)
        self.staticSizer.Add(self.text_box, 5, wx.ALL|wx.GROW|wx.EXPAND, 5)
        self.rootSizer.Add(self.staticSizer, 0, wx.ALL|wx.GROW|wx.EXPAND, 5)
        self.rootSizer.Add(self.horizontal, 0, wx.GROW|wx.EXPAND|wx.ALL|wx.ALIGN_LEFT, 5)
        
        self.buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
        self.buttonSizer.Add(self.save_button, 1, wx.GROW|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 5)
        self.buttonSizer.Add(self.cancel_button, 1, wx.GROW|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 5)
        
        self.rootSizer.Add(self.buttonSizer, 1, wx.GROW|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 5)     
        
        self.SetSizer(self.rootSizer)
        self.SetAutoLayout(True)
        self.rootSizer.Fit(self)
        self.CenterOnScreen()
        
    def __validateTemplate(self, data):
        """ Check the new email template for validity
        Params:
            data = The text control data
        """
        self.data = data
        
        # Paragraph separators
        paragraph_found = 0
        header_re = re.compile('\@HEADER')
        body_re = re.compile('\@BODY')
        footer_re = re.compile('\@FOOTER')
        
        # Value tags
        tags_found = False
        task_log_re = re.compile('\%Task\%|\%Log\%')
        
        # Parse the file
        for line in self.data.split('\n'):
            for o in (header_re, body_re, footer_re):
                if o.search(line): paragraph_found += 1
            
            if task_log_re.search(line): tags_found = True
            
        if paragraph_found != 3:
            print "Template invalid!  Missing paragraph separator"
            return
        
        if not tags_found:
            print "There are no Task or Log tags in the template"
            return
            
        self.__saveTemplate()        

    def __saveTemplate(self):
        """ Save off the edited email template file. """
        
        # Remove any existing old template file
        try:
            oldTemplate = self.templateFile+".old"
            
            if os.path.isfile(oldTemplate):
                os.remove(oldTemplate)
                
        except IOError:
            print "Unable to remove old file!\nThere will be no backup of the email template!"
            try:
                open(self.templateFile, 'w').write(self.data)
            except (IOError,OSError), error:
                print "Unable to save new email template!"
            
        else:
            
            # Copy the current template file
            current = os.getcwd()
            fullTemplate = os.path.join(current, self.templateFile)
            oldFullTemplate = os.path.join(current, oldTemplate)
            try:
                shutil.copy(fullTemplate, oldFullTemplate)
                
            except (IOError,OSError), error:
                print "Unable to make a backup of %s: (%s)" % (self.templateFile, str(error))
                print "Blowing away current template file ..."
                
            # Save the new email template
            open(self.templateFile, 'w').write(self.data)


class PreviewEmailDialog(wx.Dialog):
    def __init__(self, parent, properties):
        wx.Dialog.__init__(self, parent, -1, title="Preview Email", pos=wx.DefaultPosition, 
                            size=wx.DefaultSize, style=wx.DEFAULT_DIALOG_STYLE)
        self.properties = properties
                            
        # TODO: The layout is not right, needs fixing
        # NOTE: When the 'Send' button is clicked, everything is slurped from the dialog
        
        SEND_ID = wx.NewId()
        
        #--- PreviewEmailDialog controls ---#
        self.toLabel = wx.StaticText(self, -1, "To: ")
        self.toTextCtrl = wx.TextCtrl(self, 10, "")
        
        self.fromLabel = wx.StaticText(self, -1, "From: ")
        self.fromTextCtrl = wx.TextCtrl(self, 20, "")
        
        self.ccLabel = wx.StaticText(self, -1, "CC: ")
        self.ccTextCtrl = wx.TextCtrl(self, 30, "")
        
        self.subjectLabel = wx.StaticText(self, -1, "Subject: ")
        self.subjectTextCtrl = wx.TextCtrl(self, 40, "")
        
        self.bodyTextCtrl = wx.TextCtrl(self, 50, "", style=wx.SUNKEN_BORDER|wx.TE_MULTILINE)
        
        self.horizontal = wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL)
        
        self.sendButton = wx.Button(self, SEND_ID, "Send Email")
        self.Bind(wx.EVT_BUTTON, self.sendEmail, id=SEND_ID)
        
        self.cancelButton = wx.Button(self, wx.ID_CANCEL, "Cancel")
        
        self.__set_properties()
        self.__do_layout()
        self.ShowModal()
        
    def __set_properties(self):
        """ Populate the TextCtrls that were created above. """
        self.toTextCtrl.WriteText(self.properties.get('EMAIL.TO'))
        self.fromTextCtrl.WriteText(self.properties.get('EMAIL.FROM'))
        cc = self.properties.get('EMAIL.CC')
        if cc:
            self.ccTextCtrl(self.properties.get('EMAIL.CC'))
        
        
    def __do_layout(self):
        """ Layout controls on the dialog. """
        self.rootSizer = wx.BoxSizer(wx.VERTICAL)
        self.email_labels_sizer = wx.BoxSizer(wx.HORIZONTAL)
        
        self.toSizer = wx.BoxSizer(wx.HORIZONTAL)
        self.toSizer.Add(self.toLabel, 0, wx.ALL|wx.GROW|wx.EXPAND, 5)
        self.toSizer.Add(self.toTextCtrl, 1, wx.ALL|wx.GROW|wx.EXPAND, 5)
        self.email_labels_sizer.Add(self.toSizer, 1, 
                            wx.GROW|wx.EXPAND|wx.ALL|wx.ALIGN_TOP|wx.ALIGN_LEFT, 5)
        
        self.fromSizer = wx.BoxSizer(wx.HORIZONTAL)
        self.toSizer.Add(self.fromLabel, 0, wx.ALL|wx.GROW|wx.EXPAND, 5)
        self.toSizer.Add(self.fromTextCtrl, 1, wx.ALL|wx.GROW|wx.EXPAND, 5)
        self.email_labels_sizer.Add(self.fromSizer, 0, wx.GROW|wx.EXPAND|wx.ALL|wx.ALIGN_LEFT, 5)
        
        self.ccSizer = wx.BoxSizer(wx.HORIZONTAL)
        self.toSizer.Add(self.ccLabel, 0, wx.ALL|wx.GROW|wx.EXPAND, 5)
        self.toSizer.Add(self.ccTextCtrl, 1, wx.ALL|wx.GROW|wx.EXPAND, 5)
        self.email_labels_sizer.Add(self.ccSizer, 0, wx.GROW|wx.EXPAND|wx.ALL|wx.ALIGN_LEFT, 5)
        
        self.subjectSizer = wx.BoxSizer(wx.HORIZONTAL)
        self.toSizer.Add(self.subjectLabel, 0, wx.ALL|wx.GROW|wx.EXPAND, 5)
        self.toSizer.Add(self.subjectTextCtrl, 1, wx.ALL|wx.GROW|wx.EXPAND, 5)
        self.email_labels_sizer.Add(self.subjectSizer, 0, wx.GROW|wx.EXPAND|wx.ALL|wx.ALIGN_LEFT, 5)
        
        self.rootSizer.Add(self.email_labels_sizer, 0, wx.GROW|wx.EXPAND|wx.ALL, 10)
        self.rootSizer.Add(self.bodyTextCtrl, 0, wx.GROW|wx.EXPAND|wx.ALL, 10)
        self.rootSizer.Add(self.horizontal, 0, wx.GROW|wx.EXPAND|wx.ALL|wx.ALIGN_LEFT, 5)
        
        self.buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
        self.buttonSizer.Add(self.sendButton, 0, wx.GROW|wx.EXPAND|wx.ALL, 5)
        self.buttonSizer.Add(self.cancelButton, 0, wx.GROW|wx.EXPAND|wx.ALL, 5)
        
        self.rootSizer.Add(self.buttonSizer, 0, wx.GROW|wx.EXPAND|wx.ALL, 5)
        
        self.SetSizer(self.rootSizer)
        self.SetAutoLayout(True)
        self.rootSizer.Fit(self)
        self.CenterOnScreen()
        
    def sendEmail(self, event):
        """ Send the email. """
        print "Sending email from the PreviewEmailDialog..."
        self.Destroy()
                         
                            
class SendEmailDialog(wx.Dialog):
    def __init__(self, parent):
        wx.Dialog.__init__(self, parent, -1, title="Send Email",  size=wx.DefaultSize, 
                        pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)

        # TODO: The event handlers are not working!?!
        self.preview = False
        
        SEND_ID = wx.NewId()
        CHECK_ID = wx.NewId()
        
        # Controls
        self.sendButton = wx.Button(self, SEND_ID, "Send")
        self.Bind(wx.EVT_BUTTON, self.sendEmail, id=SEND_ID)
         
        self.cancelButton = wx.Button(self, wx.ID_CANCEL, "Cancel")
        self.horizontal = wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL)
        
        self.checkBox = wx.CheckBox(self, CHECK_ID, "Preview email before sending")
        self.Bind(wx.EVT_CHECKBOX, self.previewEmail, id=CHECK_ID)
        
        self.__do_layout()
        self.ShowModal()
        
    def __do_layout(self):
        """ Layout the controls on the dialog. """
        self.rootSizer = wx.BoxSizer(wx.VERTICAL)
        self.buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
        self.rootSizer.Add(self.checkBox, wx.GROW|wx.EXPAND|wx.ALL, 10)
        self.rootSizer.Add(self.horizontal, 0, wx.GROW|wx.EXPAND|wx.ALL, 5)
        self.buttonSizer.Add(self.sendButton, 0, wx.GROW|wx.EXPAND|wx.ALL, 5)
        self.buttonSizer.Add(self.cancelButton, 0, wx.GROW|wx.EXPAND|wx.ALL, 5)
        self.rootSizer.Add(self.buttonSizer, 0, wx.GROW|wx.EXPAND|wx.ALL|wx.ALIGN_TOP, 5)
        
        
        self.SetSizer(self.rootSizer)
        self.SetAutoLayout(True)
        self.rootSizer.Fit(self)
        self.CenterOnScreen()
    
    #--- Event Handler Methods ---#
        
    def sendEmail(self, event):
        """ Send the email. """
        print "Sending email..."
        if self.preview:
            print "Previewing email before sending..."
        self.Destroy()
        
    def previewEmail(self, event):
        """ Open the preview email dialog. """
        print "Preview the email before sending..."
        self.preview = True
        

def sendEmail(server, subject, body, to, frum, cc=None, attach=None):
    """ Send some email
    Params:
        server (the smtp server)
        subject (email subject)
        body (body of the email)
        to (this is a list, even if it is a single address)
        frum (to address)
        attach (a list of attachments, if any)
        cc (a list of CC addresses, if any)
    """
    try:
        session = smtplib.SMTP(server)

    except (smtplib.SMTPConnectError, socket.error), error:
        log.error("Problem connecting to SMTP server %s (%s)" % server)
        return 1
    
    COMMASPACE = ', '
    
    outer = MIMEMultipart()
    outer['Subject'] = subject
    outer['From'] = frum
    outer['To'] = COMMASPACE.join(to)
    
    if cc: 
        outer['CC'] = COMMASPACE.join(cc)
        
    outer.preamble = 'Message from auto-test'
    outer.epilogue = ''

    # Handle any attahcments
    if attach:
        for filename in attach:
            ctype, encoding = mimetypes.guess_type(filename)
            if ctype is None or encoding is not None:
                ctype = 'application/octet-stream'

            maintype, subtype = ctype.split('/', 1)
            if maintype == 'text':
                try:
                    fp = open(filename)
                except OSError, err:
                    err_str = "Unable to open attachment (%s) " % \
                                str(err)
                    body = body+"\n"+err_str
                    log.error("%s" % err_str)
                    continue
                else:
                    msg = MIMEText(fp.read(), _subtype=subtype)
                    fp.close()

            else:
                try:
                    fp = open(filename, 'rb')
                except OSError, err:
                    err_str = "Unable to open attachment (%s) " % \
                                str(err)
                    body = body+"\n"+err_str
                    log.error("%s" % err_str)
                    continue
                else:
                    msg = MIMEBase(maintype, subtype)
                    msg.set_payload(fp.read())
                    fp.close()

                Encoders.encode_base64(msg)

            msg.add_header('Content-Disposition', 'attachment',
                                filename=filename)
            outer.attach(msg)

        log.debug("Finished attaching files")

    else:
        log.debug("No attachments")

    # Attach the body to the MIME message
    body_text = MIMEText(body)
    outer.attach(body_text)

    # Actually send the email via SMTP
    try:
        log.debug("Sending email...")
        session.sendmail(frum, to, outer.as_string(0))

    except smtplib.SMTPSenderRefused, error:
        log.error("Sender address was refused %s" % error)

    except smtplib.SMTPRecipientsRefused, error:
        log.error("Recipient address was refused %s" % error)

    except smtplib.SMTPDataError, error:
        log.error("There was an unexpected error %s" % error)

    else:
        session.quit()
        
    return True


if __name__ == '__main__':

    # Set up the logging
	log = logging.getLogger("auto_test")
	handler = logging.StreamHandler()
	log.setLevel(logging.DEBUG)
	format = logging.Formatter("%(asctime)s %(levelname)-3s [%(module)s:%(lineno)d] %(message)s")
	handler.setFormatter(format)
	log.addHandler(handler)

	server = "exboulder2.aciesnetworks.com"
	subject = "Testing the SendEmail module"
	body = "More testing\nThis is the body"
	frum = "autoadmin@allocity.com"
	to = ['erick@allocity.com']

	data_ = {
		'Project1': {'log1.1': "some comment for this log",
					 'log1.2': "more comments for the log"
					 },
		'Project2': {'log2.1': "comments for log2.1",
					 'log2.2': "some really long comments for log2.2"
					 }
		}
	
	template_file = "myfocus_email.tmpl"
    
	
	
